import sys
import types
from evolve.engine import Evolve
from evolve.genome import Genome
from evolve.crossover import *
from evolve.mutate import *
from evolve.signals import *
from evolve.choice import randomize

def plot(coords):
    from scipy import gplt
    args = []
    for pair in coords:
        args.extend(pair)
        args.append('notitle w lp')
    gplt.plot(*args)
    gplt.title('Evolution')
    gplt.xtitle('Generation')
    gplt.ytitle('Max Fitness Value')
    gplt.output('evolution.png', 'png')

def main(args=None, options=None):
    pool = [
        Genome(0, 1, 3, 2, 4, 7, 9, 8, 5, 10, 11,
               6, 12, 15, 13, 14, 16, 18, 17, 19),
        Genome(2, 3, 1, 4, 19, 18, 15, 16, 6, 5,
               11, 14, 13, 17, 12, 7, 8, 9, 10, 0),
        Genome(16, 18, 10, 11, 13, 14, 1, 3, 2,
               0, 6, 17, 19, 7, 9, 8, 5, 12, 15, 4),
        Genome(6, 12, 15, 17, 2, 4, 7, 9, 8, 5, 19,
               0, 1, 3, 13, 14, 16, 18,  10, 11),
        ]

    SOLUTION = Genome(*range(0, 20))
    USE_TARGET = False
    LIST_SOLUTION = list(SOLUTION)
    PENALTY = -30
    BONUS = 5
    class Map(types.TupleType):

        def __new__(self, *args):
            self.count = 0
            self.previous_fit = None
            self.maxfits = []
            return super(Map, self).__new__(Map, args)

        def fitness(self, genome):
            fit = 0
            paths = 0
            consec = 0
            previous = False
            for i in xrange(0, len(genome) - 2):
                x = genome[i]
                y = genome[i+1]
                if USE_TARGET:
                    for gene, position in ((x, i), (y, i+1)):
                        if LIST_SOLUTION.index(gene) == position:
                            fit += 3 * BONUS
                        else:
                            fit -= BONUS
                if (x, y) in self:
                    paths += 1
                    fit += 1
                    if previous:
                        consec += 1
                    else:
                        fit += PENALTY
                    previous = True
                else:
                    fit += PENALTY
                    previous = False
            fit += (paths * BONUS) + (2 * consec * BONUS)
            return fit

        def stop(self, generation):
            return SOLUTION in generation

        def notify(self, signal, *args):
            if signal not in (START, FINISH):
                self.count += 1
                fits = []
                for genome in args[-1]:
                    fit = fitness(genome)
                    fits.append(fit)
                maxfit = max(fits)
                if maxfit != self.previous_fit:
                    self.maxfits.append((self.count, maxfit))
                    self.previous_fit = maxfit
                # Print the generation each 100 generations
                if not self.count % 100:
                    print 'generation %s, max fit %s' % (self.count, maxfit)
                    for genome in args[-1]:
                        print genome
            if signal == FINISH:
                print 'Solution found!'
                for generation in args[-1]:
                    for genome in generation:
                        print genome
                print 'Finished in %s generations' % self.count

    path_map = Map((0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6),
                   (6, 7), (7, 8), (8, 9), (9, 10), (10, 11),
                   (11, 12), (12, 13), (13, 14), (14, 15),
                   (15, 16), (16, 17), (17, 18), (18, 19))

    fitness = path_map.fitness
    stop = path_map.stop
    notify = path_map.notify

    mutate = RandomSwap(chance=0.015)
    crossover = NonRepeatRandomCrossover(chance=1)
    caixeiro = Evolve(fitness, pool, crossover, mutate, stop, notify)
    caixeiro.elitize = True
    caixeiro.immigrate = True
    caixeiro.run()
    return path_map.maxfits

if __name__ == '__main__':
    results = []
    for i in range(0, 1):
        fits = main()
        x_coords = [x for x, y in fits]
        y_coords = [y for x, y in fits]
        results.append((x_coords, y_coords))
    #plot(results)
